// Pixel filter, rewritten by regret

/* changelog:
 update 8: add scanline filter (ref: bsnes)
 update 7: remove hq2xbold & hq3xbold (only support 16bit)
 update 6: partially rewrite
 update 5: fix rotation
 update 4: remove asm filters
 update 3: add scanline filters (ref: gens-gs)
 update 2: add hq2xs, hq3xs, epx (ref: vba-rr, vba-m, snes9x)
 update 1: cleanup
*/

#include "burner.h"
#include "vid_filter.h"
#include "vid_support.h"

Filter filter;
ScanlineFilter scanlineFilter;

// RotateHelper
bool RotateHelper::init()
{
	image = videoInterface.getVidImage();
	w = videoInterface.getImageWidth();
	h = videoInterface.getImageHeight();

	rotate = 0;
	if (driverOkay) {
		BurnDrvGetVisibleSize(w, h, true);

		rotate = vidSupport.getRotate();
		if ((rotate & 1) && (BurnDrvGetFlags() & BDF_ORIENTATION_VERTICAL)) {
			BurnDrvGetVisibleSize(h, w, true);
		}

		if (rotate) {
			image = new unsigned char[w * h * 4];
			if (!image) {
				exit();
				return false;
			}
		}
	}
	imgpitch = w << 2;

	return true;
}

void RotateHelper::exit()
{
	if (rotate) {
		delete [] image;
	}
	image = 0;
	imgpitch = 0;
	w = h = 0;
	rotate = 0;
}

void RotateHelper::doRotate()
{
	if (rotate) {
		vidSupport.copyImageRotate(videoInterface.getVidImage(), videoInterface.getImagePitch(),
			image, imgpitch, w, h, rotate);
	}
}

RotateHelper::RotateHelper()
{
	image = 0;
	imgpitch = 0;
	w = h = 0;
	rotate = 0;
}

RotateHelper::~RotateHelper()
{
	exit();
}

// ScanlineFilter
inline unsigned ScanlineFilter::adjust(unsigned i)
{
	unsigned b = (i >>  0) & 0xff;
	unsigned g = (i >>  8) & 0xff;
	unsigned r = (i >> 16) & 0xff;

	r = (double)r * (double)intensity / 100.0;
	g = (double)g * (double)intensity / 100.0;
	b = (double)b * (double)intensity / 100.0;

	return ((b << 0) + (g << 8) + (r << 16));
}

void ScanlineFilter::render(unsigned char* srcPtr, unsigned int srcPitch, unsigned char* dstPtr, unsigned int dstPitch, unsigned width, unsigned height)
{
	if (!enabled)
		return;

	unsigned* dst = (unsigned int*)dstPtr;
	unsigned* src = (unsigned int*)srcPtr;

	dstPitch >>= 2;
	srcPitch >>= 2;
	int nextLine = (dstPitch - width) << 1;

	unsigned* line1 = dst;
	unsigned* line2 = dst + dstPitch;
	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			unsigned color = *src++;
			*line1++ = color;
			*line1++ = color;

			color = adjust(color);
			*line2++ = color;
			*line2++ = color;
		}

		// Next line.
		src += (srcPitch - width);
		line1 += nextLine;
		line2 += nextLine;
	}
}

void ScanlineFilter::setIntensity(unsigned i)
{
	bool old = enabled;

	if (i >= 100) {
		intensity = 100;
		enabled = false;
		videoInterface.reinit();
	} else {
		enabled = true;
		intensity = i;
	}

	if (old != enabled) {
		videoInterface.reinit(); // switch
	}
}

bool ScanlineFilter::init()
{
	if (!RotateHelper::init())
		return false;

	enabled = !(intensity == 100);
	return true;
}

void ScanlineFilter::exit()
{
	buffer = 0;

	RotateHelper::exit();
}

unsigned int ScanlineFilter::zoom()
{
	return enabled ? 2 : 1;
}

void ScanlineFilter::apply(unsigned char* pd, unsigned dstPitch)
{
	RotateHelper::doRotate();
	render(image, imgpitch, pd, dstPitch, w, h);
}

ScanlineFilter::ScanlineFilter()
	: enabled(false), intensity(100)
{
	buffer = 0;
}

ScanlineFilter::~ScanlineFilter()
{
	exit();
}


// ==> extra filters, added by regret
// filters from vba-rerecording and vba-m
extern void AdMame2x32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void AdMame3x32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void hq2x32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void hq2xS32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void hq3x32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void hq3xS32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern int Init_2xSaI();
extern void _2xSaI32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void Super2xSaI32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void SuperEagle32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void EPX32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
extern void EPXPlus32(unsigned char*,unsigned int,unsigned char*,unsigned char*,unsigned int,unsigned,unsigned);
// <== extra filters

// filter description, modified by regret
static struct {
	const wchar_t* name;
	int zoom;
	FilterFunc func;
} filterInfo[] = {
	{ L"None", 1, NULL },

	{ L"EPX", 2, EPX32 },
	{ L"EPX Plus", 2, EPXPlus32 },

	{ L"Scale2x", 2, AdMame2x32 },
	{ L"Scale3x", 3, AdMame3x32 },

	{ L"2xSaI", 2, _2xSaI32 },
	{ L"Super 2xSaI", 2, Super2xSaI32 },
	{ L"Super Eagle", 2, SuperEagle32 },

	{ L"HQ2x", 2, hq2x32 },
	{ L"HQ2xS", 2, hq2xS32 },

	{ L"HQ3x", 3, hq3x32 },
	{ L"HQ3xS", 3, hq3xS32 },
}; // the sequence in this structure must be refered to filter type !

// Filter class
void Filter::select(int effect)
{
	if (effect <= FILTER_NONE) {
		filterEffect = FILTER_NUMBER - 1;
	}
	else if (effect >= FILTER_NUMBER) {
		filterEffect = FILTER_NONE + 1;
	}
	else {
		filterEffect = effect;
	}

	// reinit video
	filterId = filterEffect;
	videoInterface.reinit();
	vidSNewShortMsg(filter.name(filterId));
}

const wchar_t* Filter::name(int effect)
{
	if (effect <= FILTER_NONE || effect >= FILTER_NUMBER) {
		effect = FILTER_NONE;
	}
	return filterInfo[effect].name;
}

unsigned int Filter::zoom(int effect)
{
	unsigned z = scanlineFilter.zoom();
	if (scanlineFilter.enabled) {
		return z;
	}

	if (effect <= FILTER_NONE || effect >= FILTER_NUMBER) {
		effect = FILTER_NONE;
	}
	return filterInfo[effect].zoom;
}

unsigned int Filter::depth(int effect, unsigned depth)
{
	if (effect <= FILTER_NONE || effect >= FILTER_NUMBER) {
		return 0;
	}

	if (depth == 32 && filterInfo[effect].func) {
		return depth;
	}
	return 0;
}

void Filter::exit()
{
	filterFunction = NULL;
	filterEffect = 0;

	RotateHelper::exit();
	scanlineFilter.exit();
}

int Filter::init(int effect, int rotate)
{
	scanlineFilter.init();

	if (!enable)
		return 0;

	filterEffect = effect;
	if (filterEffect == FILTER_NONE) {
		exit();
		enable = 0;
		return 0;
	}

//	if (depth(filterEffect, 32) == 0) {
//		exit();
//		enable = 0;
//		return 1;
//	}

	if (!RotateHelper::init())
		return 1;

	// init filter function
	filterFunction = filterInfo[filterEffect].func;

	if (filterFunction) {
		switch (filterEffect) {
			case FILTER_2XSAI:
			case FILTER_SUPER2XSAI:
			case FILTER_SUPEREAGLE:
				Init_2xSaI();
				break;
			default:
				break;
		}
	}

   	dprintf(("  * Filter initialised: using %s in %i-bit mode.\n"), filterInfo[filterEffect].name, 32);

	return 0;
}

int Filter::scale(RECT* rect, unsigned width, unsigned height)
{
	int scaleWidth = rect->right - rect->left;
	int scaleHeight = rect->bottom - rect->top;

	int filterEnlarge = 0;
	if (scaleWidth >= (width * filterInfo[filterEffect].zoom)
		&& scaleHeight >= (height * filterInfo[filterEffect].zoom)) {
		scaleWidth = width * filterInfo[filterEffect].zoom;
		scaleHeight = height * filterInfo[filterEffect].zoom;
		filterEnlarge = 1;
	}

	if (!filterEnlarge) {
		scaleWidth = width;
		scaleHeight = height;
	}

	rect->left = (rect->right + rect->left) / 2;
	rect->left -= scaleWidth / 2;
	rect->right = rect->left + scaleWidth;

	rect->top = (rect->bottom + rect->top) / 2;
	rect->top -= scaleHeight / 2;
	rect->bottom = rect->top + scaleHeight;

	return 0;
}

int Filter::apply(unsigned char* pd, unsigned pitch)
{
	if (!pd) {
		return 1;
	}

	// scanline filter
	if (scanlineFilter.enabled) {
		scanlineFilter.apply(pd, pitch);
		return 0;
	}

	// direct copy
	if (!enable) {
		vidSupport.copyImage(pd, pitch);
		vidSupport.postCopyImage(pd, pitch);
		return 0;
	}

	// other filter
	RotateHelper::doRotate();

	if (filterFunction) {
		filterFunction(image, imgpitch, NULL, pd, pitch, w, h);
		return 0;
	}
	return 1;
}

Filter::Filter()
	: filterFunction(NULL), filterEffect(0)
{
	filterId = FILTER_NONE;
	linear = 1;
	enable = false;
}

Filter::~Filter()
{
	exit();
}
